home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
AmigActive 23
/
AACD 23.iso
/
AACD
/
Online
/
opennap
/
join.c
< prev
next >
Wrap
C/C++ Source or Header
|
2001-06-08
|
18KB
|
686 lines
/* Copyright (C) 2000-1 drscholl@users.sourceforge.net
This is free software distributed under the terms of the
GNU Public License. See the file COPYING for details.
$Id: join.c,v 1.85 2001/02/15 08:39:45 drscholl Exp $ */
#ifndef WIN32
#include <unistd.h>
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "opennap.h"
#include "debug.h"
/* ensure the channel name contains only valid characters */
int
invalid_channel (const char *s)
{
int count = 0;
if (option (ON_IRC_CHANNELS) && *s != '#' && *s != '&')
return 1; /* must start with # or & */
s++;
while (*s)
{
if (*s < '!' || *s > '~' || strchr ("%$*?\",", *s))
return 1;
count++;
s++;
}
return ((count == 0)
|| (Max_Channel_Length > 0 && count > Max_Channel_Length));
}
static BAN *
is_banned (LIST * bans, const char *nick, const char *host)
{
char mask[256];
snprintf (mask, sizeof (mask), "%s!%s", nick, host);
for (; bans; bans = bans->next)
{
if (glob_match (((BAN *) bans->data)->target, mask))
return bans->data;
}
return 0;
}
/* this function gets called when we see a JOIN from a remote server that
* shouldn't have happened, such as a channel full, +INVITE or a ban. we
* need to send back a KICK to make sure all servers stay synched.
*/
static void
join_desync (CONNECTION * con, const char *chan, USER * user,
const char *reason)
{
log ("join_desync: server %s is desynced", con->host);
send_cmd (con, MSG_CLIENT_KICK, ":%s %s %s \"%s\"",
Server_Name, chan, user->nick, reason);
}
/* handle client request to join channel */
/* [ :<nick> ] <channel> */
HANDLER (join)
{
USER *user;
CHANNEL *chan;
LIST *list;
CHANUSER *chanUser, *cu;
int chanop = 0;
int local = 0;
char chanbuf[256]; /* needed when creating a rollover channel */
char *chan_name;
char *sender_name;
(void) tag;
(void) len;
ASSERT (validate_connection (con));
if (pop_user_server (con, tag, &pkt, &sender_name, &user) != 0)
return;
ASSERT (validate_user (user));
chan_name = next_arg (&pkt);
if (!chan_name)
{
unparsable (con);
return;
}
/* this loop is here in case the channel has a limit so we can create
the rollover channels */
ASSERT (sizeof (chanbuf) >= (unsigned int) Max_Channel_Length);
chanbuf[sizeof (chanbuf) - 1] = 0;
/* automatically prepend # to channel names if missing */
if (option (ON_IRC_CHANNELS) && *chan_name != '#' && *chan_name != '&')
{
if (ISUSER (con))
{
/* for older clients that still let channels through with no
* prefix, automatically prepend it here. most clients seem to
* be able to deal with the join for a different channel being
* set back to them
*/
snprintf (chanbuf, sizeof (chanbuf), "#%s", chan_name);
chan_name = chanbuf;
}
else
{
/* peer server shouldn't have let this through, so we will
* reject it.
*/
join_desync (con, chan_name, user, "invalid channel name");
return;
}
}
/* check if this is a local channel */
local = (*chan_name == '&');
if (local && ISSERVER (con))
{
log ("join: server %s joined local channel %s",
user->nick, user->server, chan_name);
return;
}
if (user->level < LEVEL_MODERATOR)
{
/* enforce a maximum channels per user */
/* TODO: if linked servers have different settings, the channel
membership could become desynched */
if (list_count (user->channels) >= Max_User_Channels)
{
if (ISUSER (con))
send_cmd (con, MSG_SERVER_NOSUCH,
"channel join failed: you may only join %d channels",
Max_User_Channels);
else
join_desync (con, chan_name, user, "joined max channels");
return;
}
if (user->muzzled)
{
if (ISUSER (con))
send_cmd (con, MSG_SERVER_NOSUCH,
"channel join failed: can't join channels while muzzled");
else
join_desync (con, chan_name, user, "user is muzzled");
return;
}
}
for (;;)
{
chan = hash_lookup (Channels, chan_name);
if (!chan)
{
/* check if this server allows normals to create channels */
if ((Server_Flags & ON_STRICT_CHANNELS) &&
user->level < LEVEL_MODERATOR)
{
if (ISUSER (con))
send_cmd (con, MSG_SERVER_NOSUCH,
"channel join failed: permission denied");
else
join_desync (con, chan_name, user,
"can't create channels");
return;
}
if (invalid_channel (chan_name))
{
if (ISUSER (con))
send_cmd (con, MSG_SERVER_NOSUCH,
"channel join failed: invalid channel");
else
join_desync (con, chan_name, user,
"invalid channel name");
return;
}
chan = new_channel ();
if (!chan)
return; /* out of memory */
#if DEBUG
chan->magic = MAGIC_CHANNEL;
#endif
chan->name = STRDUP (chan_name);
if (!chan->name)
{
OUTOFMEMORY ("join");
FREE (chan);
return;
}
chan->local = local;
chan->level = LEVEL_USER;
#if LOG_CHANNEL
if (local && !strcasecmp ("&LOG", chan->name))
{
chan->flags |= ON_CHANNEL_QUIET;
chan->level = LEVEL_ADMIN;
}
#endif
/* we only set the timestamp if a local user creates the
* channel. otherwise we have to get it from the remote
* server since our clocks might not be synched.
*/
if (ISUSER (con))
chan->timestamp = global.current_time;
/* set the default topic */
snprintf (Buf, sizeof (Buf), "Welcome to the %s channel.",
chan->name);
chan->topic = STRDUP (Buf);
if (!chan->topic)
{
OUTOFMEMORY ("join");
FREE (chan->name);
FREE (chan);
return;
}
hash_add (Channels, chan->name, chan);
log ("join: creating channel %s", chan->name);
if (ISUSER (con))
{
/* the first user to enter a channel gets ops. note that
* predefined channels with no users never give out ops.
* this is to prevent people from trying to get ops by
* riding splits. we also only set chanop when a local
* user creates the channel. this is to avoid problems when
* syncing servers since the first join message we get is
* not necessarily the first person who entered the channel
* on the remote side. we let the remote server tell us
* who is opped.
*/
chanop = 1;
}
}
/* ensure that this user isn't already on this channel */
else if (list_find (user->channels, chan))
{
if (ISUSER (con))
send_cmd (con, MSG_SERVER_NOSUCH,
"channel join failed: already joined channel");
/* no need to correct desync since we already have the user in
* the channel.
*/
return;
}
/* check to make sure the user has privilege to join */
else if (user->level < chan->level)
{
if (ISUSER (con))
send_cmd (con, MSG_SERVER_NOSUCH,
"channel join failed: requires level %s",
Levels[chan->level]);
else
join_desync (con, chan->name, user, "not required level");
return;
}
else
{
/* if not mod+, check extra permissions */
if (user->level < LEVEL_MODERATOR)
{
BAN *ban;
/* check to make sure this user is not banned from the channel */
if (
(ban =
is_banned (chan->bans, user->nick, my_ntoa (BSWAP32 (user->ip)))))
{
if (ISUSER (user->con))
{
send_cmd (user->con, MSG_SERVER_NOSUCH,
"channel join failed: banned: %s",
NONULL (ban->reason));
}
else
join_desync (con, chan->name, user,
"banned from channel");
return;
}
/* check for invitation */
if ((chan->flags & ON_CHANNEL_INVITE) &&
!list_find (chan->invited, user))
{
if (ISUSER (con))
send_cmd (con, MSG_SERVER_NOSUCH,
"channel join failed: invite only");
else
join_desync (con, chan->name, user, "no invite");
return;
}
if (chan->limit > 0
&& list_count (chan->users) >= chan->limit)
{
if ((chan->flags & ON_CHANNEL_REGISTERED) == 0)
{
/* don't create rollover channels for non-registered
* channels.
*/
if (ISUSER (con))
send_cmd (con, MSG_SERVER_NOSUCH,
"channel join failed: channel full");
else
join_desync (con, chan->name, user,
"channel full");
return;
}
/* for predefined channels, automatically create a rollover
channel when full */
else
{
char *p;
int n = 1;
if (chan_name != chanbuf)
{
strncpy (chanbuf, chan_name,
sizeof (chanbuf) - 1);
chan_name = chanbuf;
}
p = chanbuf + strlen (chanbuf);
#define ISDIGIT(c) ((c)>=0 && (c)<='9')
while (p > chanbuf && ISDIGIT (*(p - 1)))
p--;
if (ISDIGIT (*p))
{
n = atoi (p);
*p = 0;
}
snprintf (chanbuf + strlen (chanbuf),
sizeof (chanbuf) - strlen (chanbuf), "%d",
n + 1);
log ("join: trying channel %s", chanbuf);
continue;
}
}
}
}
break;
}
ASSERT (validate_channel (chan));
/* clean up invite lists - do this even when not +INVITE just in case
* it was present and then the channel was set to -INVITE
*/
if (chan->invited)
chan->invited = list_delete (chan->invited, user);
/* add this channel to the list of this user is subscribed to */
list = MALLOC (sizeof (LIST));
if (!list)
{
OUTOFMEMORY ("join");
goto error;
}
list->data = chan;
list->next = user->channels;
user->channels = list;
/* add this user to the channel members list */
chanUser = CALLOC (1, sizeof (CHANUSER));
#if DEBUG
chanUser->magic = MAGIC_CHANUSER;
#endif
chanUser->user = user;
list = MALLOC (sizeof (LIST));
if (!list)
{
OUTOFMEMORY ("join");
goto error;
}
list->data = chanUser;
list->next = chan->users;
chan->users = list;
/* if there are linked servers, send this message along */
if (!chan->local)
pass_message_args (con, tag, ":%s %s", user->nick, chan->name);
/* if local user send an ack for the join */
if (ISUSER (con))
{
/* notify client of success */
send_cmd (con, MSG_SERVER_JOIN_ACK, "%s", chan->name);
if ((chan->flags & ON_CHANNEL_QUIET) == 0)
{
/* send the client the list of current users in the channel */
for (list = chan->users; list; list = list->next)
{
cu = list->data;
ASSERT (cu != 0);
ASSERT (cu->magic == MAGIC_CHANUSER);
if (!cu->user->cloaked || user->level >= LEVEL_MODERATOR)
send_cmd (con, MSG_SERVER_CHANNEL_USER_LIST /* 408 */ ,
"%s %s %d %d", chan->name, cu->user->nick,
cu->user->shared, cu->user->speed);
}
}
}
if ((chan->flags & ON_CHANNEL_QUIET) == 0)
{
/* notify members of the channel that this user has joined */
for (list = chan->users; list; list = list->next)
{
cu = list->data;
ASSERT (cu != 0);
ASSERT (cu->magic == MAGIC_CHANUSER);
if (ISUSER (cu->user->con) && cu->user != user &&
(!user->cloaked || cu->user->level >= LEVEL_MODERATOR))
send_cmd (cu->user->con, MSG_SERVER_JOIN, "%s %s %d %d",
chan->name, user->nick, user->shared, user->speed);
}
}
/* notify ops/mods+ of this users status */
if (chanop)
{
notify_ops (chan, "Server %s set %s as operator on channel %s",
Server_Name, user->nick, chan->name);
/* set the flag after the notice so the user isn't notified
* twice.
*/
chanUser->flags |= ON_CHANNEL_OPERATOR;
/* broadcast op message to all servers. this should *only* happen
* when a local user creates a new channel. the reason we do this
* is to solve the problem of server linking when the first join
* message we get is not necessarily a channel op. so we rely on
* the remote server to tell us which users are opped.
*/
ASSERT (ISUSER (con));
if (!chan->local)
pass_message_args (NULL, MSG_CLIENT_OP, ":%s %s %s :%u",
Server_Name, chan->name, user->nick,
chan->timestamp);
}
if (ISUSER (con))
{
if ((chan->flags & ON_CHANNEL_QUIET) == 0)
{
/* send end of channel list message */
/* NOTE: for some reason this is the way the napster.com servers send
the messages. I'm not sure why they send the end of channel list
AFTER the join message for yourself */
send_cmd (con, MSG_SERVER_CHANNEL_USER_LIST_END /*409 */ , "%s",
chan->name);
}
/* send channel topic */
ASSERT (chan->topic != 0);
send_cmd (con, MSG_SERVER_TOPIC /*410 */ , "%s %s", chan->name,
chan->topic);
if (chanop)
send_cmd (con, MSG_SERVER_NOSUCH,
"Server %s set you as operator on channel %s",
Server_Name, chan->name);
}
return;
error:
/* set things back to a sane state */
chan->users = list_delete (chan->users, user);
user->channels = list_delete (user->channels, chan);
if (!chan->users)
{
log ("join: destroying channel %s", chan->name);
hash_remove (Channels, chan->name);
}
return;
}
/* 823 [ :<sender> ] <channel> [level [timestamp]]
* queries/sets the minimum user level required to enter a channel
*/
HANDLER (channel_level)
{
int level;
char *sender;
USER *senderUser;
int ac = -1;
char *av[3];
CHANNEL *chan;
int desync = 0;
(void) tag;
(void) len;
ASSERT (validate_connection (con));
if (pop_user_server (con, tag, &pkt, &sender, &senderUser))
return;
if (pkt)
ac = split_line (av, sizeof (av) / sizeof (char), pkt);
if (ac < 1)
{
print_args (ac, av);
unparsable (con);
return;
}
chan = hash_lookup (Channels, av[0]);
if (!chan)
{
nosuchchannel (con);
return;
}
ASSERT (validate_channel);
if (ac == 1)
{
/* query the current mode */
CHECK_USER_CLASS ("channel_level");
send_cmd (con, MSG_SERVER_NOSUCH, "Channel %s is level %s",
chan->name, Levels[chan->level]);
return;
}
level = get_level (av[1]);
if (level == -1)
{
if (ISUSER (con))
send_cmd (con, MSG_SERVER_NOSUCH,
"channel level failed: invalid level");
return;
}
if (chan->level == level)
return; /* same value, ignore */
if (ISSERVER (con) && chan->local)
{
log ("channel_level: server %s accessed local channel %s",
con->host, chan->name);
return;
}
/* check for permission */
if (senderUser && senderUser->level < LEVEL_MODERATOR
&& !is_chanop (chan, senderUser))
{
if (ISUSER (con))
{
send_cmd (con, MSG_SERVER_NOSUCH,
"channel level failed: you are not channel operator");
return;
}
desync = 1;
}
/* check the TS if present */
else if (ISSERVER (con) && ac > 2)
{
time_t ts = atoi (av[2]);
if (chan->timestamp > 0 && (ts == 0 || ts > chan->timestamp))
desync = 1;
else
chan->timestamp = ts;
}
if (desync)
{
/* detected server desync, correct the mode on the remote server */
log ("channel_level: server %s is desynced", con->host);
send_cmd (con, tag, ":%s %s %s %u",
Server_Name, chan->name, Levels[chan->level],
chan->timestamp);
return;
}
if (!chan->local)
pass_message_args (con, tag, ":%s %s %s %u",
sender, chan->name, Levels[level],
chan->timestamp);
chan->level = level;
notify_ops (chan, "%s set channel %s to level %s",
sender, chan->name, Levels[level]);
}
/* 826 [:sender] <channel> [limit [timestamp]]
* queries/sets the max number of users on a channel
*/
HANDLER (channel_limit)
{
int ac = -1;
int limit;
int desync = 0;
char *av[3];
char *sender;
USER *senderUser;
CHANNEL *chan;
ASSERT (validate_connection (con));
(void) len;
if (pop_user_server (con, tag, &pkt, &sender, &senderUser))
return;
if (pkt)
ac = split_line (av, FIELDS (av), pkt);
if (ac < 1)
{
unparsable (con);
return;
}
chan = hash_lookup (Channels, av[0]);
if (!chan)
{
nosuchchannel (con);
return;
}
if (ac == 1)
{
/*query current limit */
CHECK_USER_CLASS ("channel_limit");
send_cmd (con, MSG_SERVER_NOSUCH, "Channel %s has limit %d",
chan->name, chan->limit);
return;
}
if (ISSERVER (con) && chan->local)
{
log ("channel_limit: server %s accessed local channel %s",
con->host, chan->name);
return;
}
limit = atoi (av[1]);
if (limit < 0 || limit > 65535)
{
if (ISUSER (con))
send_cmd (con, MSG_SERVER_NOSUCH,
"channel limit failed: invalid limit");
return;
}
if (senderUser && senderUser->level < LEVEL_MODERATOR
&& !is_chanop (chan, senderUser))
{
if (ISUSER (con))
{
send_cmd (con, MSG_SERVER_NOSUCH,
"channel limit failed: you are not channel operator");
return;
}
desync = 1;
}
/* check timestamp */
else if (ISSERVER (con) && ac > 2)
{
time_t ts = atoi (av[2]);
if (chan->timestamp > 0 && (ts == 0 || ts > chan->timestamp))
desync = 1;
else
chan->timestamp = ts;
}
if (desync)
{
/* server is out of sync, reset its limit */
log ("channel_limit: server %s is desynced", con->host);
send_cmd (con, tag, ":%s %s %d %u",
Server_Name, chan->name, chan->limit, chan->timestamp);
return;
}
/* wait until now to check for this so that if a remote server has
* a different timestamp we can sync that even if the value is the
* same
*/
if (chan->limit == limit)
return; /* same value, just ignore it */
chan->limit = limit;
if (!chan->local)
pass_message_args (con, tag, ":%s %s %d %u", sender, chan->name,
limit, chan->timestamp);
notify_ops (chan, "%s set limit on channel %s to %d", sender, chan->name,
limit);
}